﻿using System.Text.RegularExpressions;

namespace VideoTools.SubtitleEdit.Common
{
    /// <summary>
    /// HTML specific string manipulations.
    /// </summary>
    public static class HtmlUtil
    {
        public static string TagItalic => "i";
        public static string TagBold => "b";
        public static string TagUnderline => "u";
        public static string TagCyrillicI => "\u0456"; // Cyrillic Small Letter Byelorussian-Ukrainian i (http://graphemica.com/%D1%96)

        private static readonly Regex TagOpenRegex = new Regex(@"<\s*(?:/\s*)?(\w+)[^>]*>", RegexOptions.Compiled);

        /// <summary>
        /// Remove all of the specified opening and closing tags from the source HTML string.
        /// </summary>
        /// <param name="source">The source string to search for specified HTML tags.</param>
        /// <param name="tags">The HTML tags to remove.</param>
        /// <returns>A new string without the specified opening and closing tags.</returns>
        public static string RemoveOpenCloseTags(string source, params string[] tags)
        {
            if (string.IsNullOrEmpty(source) || source.IndexOf('<') < 0)
            {
                return source;
            }

            // This pattern matches these tag formats:
            // <tag*>
            // < tag*>
            // </tag*>
            // < /tag*>
            // </ tag*>
            // < / tag*>
            return TagOpenRegex.Replace(source, m => tags.Contains(m.Groups[1].Value, StringComparer.OrdinalIgnoreCase) ? string.Empty : m.Value);
        }

        public static string RemoveHtmlTags(string input, bool alsoSsaTags = false)
        {
            if (input == null || input.Length < 3)
            {
                return input;
            }

            var s = input;
  

            if (s.IndexOf('<') < 0)
            {
                return s;
            }

            if (s.Contains("< "))
            {
                s = FixInvalidItalicTags(s);
            }

            if (s.IndexOf('x') > 0)
            {
                s = s.Replace("<box>", string.Empty).Replace("</box>", string.Empty);
            }

            // v tag from WebVTT
            var indexOfVTag = s.IndexOf("<v ", StringComparison.Ordinal);
            if (indexOfVTag < 0)
            {
                indexOfVTag = s.IndexOf("<v.", StringComparison.Ordinal);
            }
            if (indexOfVTag >= 0)
            {
                var indexOfEndVTag = s.IndexOf('>', indexOfVTag);
                if (indexOfEndVTag >= 0)
                {
                    s = s.Remove(indexOfVTag, indexOfEndVTag - indexOfVTag + 1);
                    s = s.Replace("</v>", string.Empty);
                }
            }

            // v tag from WebVTT
            var indexOfCTag = s.IndexOf("<c.", StringComparison.Ordinal);
            if (indexOfCTag >= 0)
            {
                var indexOfEndVTag = s.IndexOf('>', indexOfCTag);
                if (indexOfEndVTag >= 0)
                {
                    s = s.Remove(indexOfCTag, indexOfEndVTag - indexOfCTag + 1);
                    s = s.Replace("</c>", string.Empty);
                }
            }

            return RemoveCommonHtmlTags(s);
        }

        /// <summary>
        /// Optimized method to remove common html tags, like <i>, <b>, <u>, and <font>
        /// </summary>
        /// <param name="s">Text to remove html tags from</param>
        /// <returns>Text stripped from common html tags</returns>
        private static string RemoveCommonHtmlTags(string s)
        {
            char[] array = new char[s.Length];
            int arrayIndex = 0;
            bool inside = false;

            for (int i = 0; i < s.Length; i++)
            {
                char ch = s[i];
                if (ch == '<' && i < s.Length - 2)
                {
                    var next = s[i + 1];
                    var nextNext = s[i + 2];
                    if (nextNext == '>' &&
                        (next == 'i' || // <i>
                         next == 'I' || // <I>
                         next == 'b' || // <b>
                         next == 'B' || // <B>
                         next == 'u' || // <u>
                         next == 'U'))  // <U>
                    {
                        inside = true;
                        continue;
                    }

                    if (next == '/' && i < s.Length - 3)
                    {
                        var nextNextNext = s[i + 3];
                        if (nextNextNext == '>' &&
                            (nextNext == 'i' || // </i>
                             nextNext == 'I' || // </I>
                             nextNext == 'b' || // </b>
                             nextNext == 'B' || // </B>
                             nextNext == 'u' || // </u>
                             nextNext == 'U'))  // </U>
                        {
                            inside = true;
                            continue;
                        }

                        if (nextNext == 'c' && nextNextNext == '.')
                        {
                            inside = true;
                            continue;
                        }
                    }

                    if (nextNext == '/' && i < s.Length - 3)
                    { // some bad end tags sometimes seen
                        var nextNextNext = s[i + 3];
                        if (nextNextNext == '>' &&
                            (next == 'i' || // <i/>
                             next == 'I' || // <I/>
                             next == 'b' || // <b/>
                             next == 'B' || // <B/>
                             next == 'u' || // <u/>
                             next == 'U'))  // <U/>
                        {
                            inside = true;
                            continue;
                        }
                    }

                    if ((next == 'f' || next == 'F') && s.Substring(i).StartsWith("<font", StringComparison.OrdinalIgnoreCase) || // <font
                        next == '/' && (nextNext == 'f' || nextNext == 'F') && s.Substring(i).StartsWith("</font>", StringComparison.OrdinalIgnoreCase) ||  // </font>                        
                        next == ' ' && nextNext == '/' && s.Substring(i).StartsWith("< /font>", StringComparison.OrdinalIgnoreCase) ||  // < /font>
                        next == '/' && nextNext == ' ' && s.Substring(i).StartsWith("</ font>", StringComparison.OrdinalIgnoreCase))  // </ font>
                    {
                        inside = true;
                        continue;
                    }

                    if (next == 'c' && nextNext == '.')
                    {
                        inside = true;
                        continue;
                    }
                }
                if (inside && ch == '>')
                {
                    inside = false;
                    continue;
                }
                if (!inside)
                {
                    array[arrayIndex] = ch;
                    arrayIndex++;
                }
            }
            return new string(array, 0, arrayIndex);
        }


        private static readonly string[] UppercaseTags = { "<I>", "<U>", "<B>", "<FONT", "</I>", "</U>", "</B>", "</FONT>" };

        public static string FixUpperTags(string input)
        {
            if (string.IsNullOrEmpty(input) || input.IndexOf('<') < 0)
            {
                return input;
            }

            var text = input;
            var idx = text.IndexOfAny(UppercaseTags, StringComparison.Ordinal);
            while (idx >= 0)
            {
                var endIdx = text.IndexOf('>', idx + 2);
                if (endIdx < idx)
                {
                    break;
                }

                var tag = text.Substring(idx, endIdx - idx).ToLowerInvariant();
                text = text.Remove(idx, endIdx - idx).Insert(idx, tag);
                idx = text.IndexOfAny(UppercaseTags, StringComparison.Ordinal);
            }
            return text;
        }

        public static bool IsTextFormattable(in string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return false;
            }

            var len = text.Length;
            var index = 0;
            while (index < len && text[index] == '<')
            {
                index = text.IndexOf('>', index + 1);
                if (index < 0) break;
                index += 1;
            }

            // buggy text of no closing present
            index = Math.Max(0, index);

            var fromLenIdx = len - 1;
            while (fromLenIdx >= 0 && text[fromLenIdx] == '>')
            {
                fromLenIdx = text.LastIndexOf('<', fromLenIdx);
                if (fromLenIdx < 0) break;
                fromLenIdx--;
            }


            /* 项目“LibSE (netstandard2.1)”的未合并的更改
            在此之前:
                        fromLenIdx = fromLenIdx > 0 ? fromLenIdx : len - 1;

                        // no formattable text in between
            在此之后:
                        fromLenIdx = fromLenIdx > 0 ? fromLenIdx : len - 1;

                        // no formattable text in between
            */
            fromLenIdx = fromLenIdx > 0 ? fromLenIdx : len - 1;

            // no formattable text in between
            if (fromLenIdx < index)
            {
                return false;
            }

            for (var i = index; i <= fromLenIdx; i++)
            {
                if (char.IsLetterOrDigit(text[i]))
                {
                    return true;
                }
            }

            return false;

            /* 项目“LibSE (netstandard2.1)”的未合并的更改
            在此之前:
                    }

                    public static string FixInvalidItalicTags(string input)
            在此之后:
                    }

                    public static string FixInvalidItalicTags(string input)
            */
        }

        public static string FixInvalidItalicTags(string input)
        {
            var text = input;

            var preTags = string.Empty;
            if (text.StartsWith("{\\", StringComparison.Ordinal))
            {
                var endIdx = text.IndexOf('}', 2);
                if (endIdx > 2)
                {
                    preTags = text.Substring(0, endIdx + 1);
                    text = text.Remove(0, endIdx + 1);
                }
            }

            const string beginTag = "<i>";
            const string endTag = "</i>";

            text = text.Replace("< i >", beginTag);
            text = text.Replace("< i>", beginTag);
            text = text.Replace("<i >", beginTag);
            text = text.Replace("< I >", beginTag);
            text = text.Replace("< I>", beginTag);
            text = text.Replace("<I >", beginTag);
            text = text.Replace("<i<", beginTag);
            text = text.Replace("<I<", beginTag);

            text = text.Replace("< / i >", endTag);
            text = text.Replace("< /i>", endTag);
            text = text.Replace("</ i>", endTag);
            text = text.Replace("< /i >", endTag);
            text = text.Replace("</i >", endTag);
            text = text.Replace("</ i >", endTag);
            text = text.Replace("< / i>", endTag);
            text = text.Replace("</I>", endTag);
            text = text.Replace("< / I >", endTag);
            text = text.Replace("< /I>", endTag);
            text = text.Replace("</ I>", endTag);
            text = text.Replace("< /I >", endTag);
            text = text.Replace("</I >", endTag);
            text = text.Replace("</ I >", endTag);
            text = text.Replace("< / I>", endTag);
            text = text.Replace("</i<", endTag);
            text = text.Replace("</I<", endTag);

            text = text.Replace("<I>", beginTag);
            text = text.Replace("</I>", endTag);

            text = text.Replace("</i> <i>", "_@_");
            text = text.Replace(" _@_", "_@_");
            text = text.Replace(" _@_ ", "_@_");
            text = text.Replace("_@_", " ");

            if (text.Contains(beginTag))
            {
                text = text.Replace("<i/>", endTag);
                text = text.Replace("<I/>", endTag);
            }
            else
            {
                text = text.Replace("<i/>", string.Empty);
                text = text.Replace("<I/>", string.Empty);
            }

            text = text.Replace("]<i> ", "] <i>");
            text = text.Replace(")<i> ", ") <i>");
            text = text.Replace("] </i>", "] </i>");
            text = text.Replace(") </i>", ") </i>");

            text = text.Replace(beginTag + beginTag, beginTag);
            text = text.Replace(endTag + endTag, endTag);

            var italicBeginTagCount = Utilities.CountTagInText(text, beginTag);
            var italicEndTagCount = Utilities.CountTagInText(text, endTag);
            var noOfLines = Utilities.GetNumberOfLines(text);
            if (italicBeginTagCount + italicEndTagCount == 0)
            {
                return preTags + text;
            }

            if (italicBeginTagCount == 1 && italicEndTagCount == 1 && text.IndexOf(beginTag, StringComparison.Ordinal) > text.IndexOf(endTag, StringComparison.Ordinal))
            {
                const string pattern = "___________@";
                text = text.Replace(beginTag, pattern);
                text = text.Replace(endTag, beginTag);
                text = text.Replace(pattern, endTag);
            }

            if (italicBeginTagCount == 2 && italicEndTagCount == 0)
            {
                var firstIndex = text.IndexOf(beginTag, StringComparison.Ordinal);
                var lastIndex = text.LastIndexOf(beginTag, StringComparison.Ordinal);
                var lastIndexWithNewLine = text.LastIndexOf(Environment.NewLine + beginTag, StringComparison.Ordinal) + Environment.NewLine.Length;
                if (noOfLines == 2 && lastIndex == lastIndexWithNewLine && firstIndex < 2)
                {
                    text = text.Replace(Environment.NewLine, endTag + Environment.NewLine) + endTag;
                }
                else
                {
                    text = text.Remove(lastIndex, beginTag.Length).Insert(lastIndex, endTag);
                }
            }

            if (italicBeginTagCount == 1 && italicEndTagCount == 2)
            {
                var firstIndex = text.IndexOf(endTag, StringComparison.Ordinal);
                if (text.StartsWith("</i>-<i>-", StringComparison.Ordinal) ||
                    text.StartsWith("</i>- <i>-", StringComparison.Ordinal) ||
                    text.StartsWith("</i>- <i> -", StringComparison.Ordinal) ||
                    text.StartsWith("</i>-<i> -", StringComparison.Ordinal))
                {
                    text = text.Remove(0, 5);
                }
                else if (firstIndex == 0)
                {
                    text = text.Remove(0, 4);
                }
                else
                {
                    text = text.Substring(0, firstIndex) + text.Substring(firstIndex + endTag.Length);
                }
            }

            if (italicBeginTagCount == 2 && italicEndTagCount == 1)
            {
                var lines = text.SplitToLines();
                if (lines.Count == 2 && lines[0].StartsWith(beginTag, StringComparison.Ordinal) && lines[0].EndsWith(endTag, StringComparison.Ordinal) &&
                    lines[1].StartsWith(beginTag, StringComparison.Ordinal))
                {
                    text = text.TrimEnd() + endTag;
                }
                else
                {
                    var lastIndex = text.LastIndexOf(beginTag, StringComparison.Ordinal);
                    if (text.Length > lastIndex + endTag.Length)
                    {
                        text = text.Substring(0, lastIndex) + text.Substring(lastIndex - 1 + endTag.Length);
                    }
                    else
                    {
                        text = text.Substring(0, lastIndex - 1) + endTag;
                    }
                }
                if (text.StartsWith(beginTag, StringComparison.Ordinal) && text.EndsWith(endTag, StringComparison.Ordinal) && text.Contains(endTag + Environment.NewLine + beginTag))
                {
                    text = text.Replace(endTag + Environment.NewLine + beginTag, Environment.NewLine);
                }
            }

            if (italicBeginTagCount == 1 && italicEndTagCount == 0)
            {
                var lines = text.SplitToLines();
                var sc = StringComparison.Ordinal;
                for (int i = 0; i < lines.Count; i++)
                {
                    var line = lines[i];

                    /* 项目“LibSE (netstandard2.1)”的未合并的更改
                    在此之前:
                                        var italicIndex = line.LastIndexOf(beginTag, StringComparison.Ordinal);

                                        // no italic in current 'i' line, try next
                    在此之后:
                                        var italicIndex = line.LastIndexOf(beginTag, StringComparison.Ordinal);

                                        // no italic in current 'i' line, try next
                    */
                    var italicIndex = line.LastIndexOf(beginTag, StringComparison.Ordinal);

                    // no italic in current 'i' line, try next
                    if (italicIndex < 0)
                    {
                        continue;
                    }

                    // try earlier insert if possible e.g: <b><i>foobar</b> => <b><i>foobar</i></b>
                    lines[i] = IsTextFormattable(line.Substring(italicIndex + 3))
                        ? line.Insert(CalculateEarlyInsertIndex(line), endTag)
                        : line.Replace(beginTag, string.Empty);

                    break; // break as soon as we reach here since italicBeginTagCount == 1

                    int CalculateEarlyInsertIndex(string s)
                    {
                        var len = s.Length;
                        var lastClosingTagIndex = s.LastIndexOf("</", len - 1, len - italicIndex - 3, sc);
                        while (lastClosingTagIndex > italicIndex + 3)
                        {
                            var tempClosingIdx = s.LastIndexOf("</", lastClosingTagIndex, lastClosingTagIndex - italicIndex - 3, sc);
                            if (tempClosingIdx < 0) break;
                            lastClosingTagIndex = tempClosingIdx;
                        }
                        // try finding first closing tag index and insert the new closing there
                        // to avoid having text with closed tags like <b><i>foo</b></i>
                        return lastClosingTagIndex > italicIndex ? lastClosingTagIndex : len;
                    }
                }

                // reconstruct the text from lines
                text = string.Join(Environment.NewLine, lines);
            }

            if (italicBeginTagCount == 0 && italicEndTagCount == 1)
            {
                var cleanText = RemoveOpenCloseTags(text, TagItalic, TagBold, TagUnderline, TagCyrillicI);
                var isFixed = false;

                // Foo.</i>
                if (text.EndsWith(endTag, StringComparison.Ordinal) && !cleanText.StartsWith('-') && !cleanText.Contains(Environment.NewLine + "-"))
                {
                    text = beginTag + text;
                    isFixed = true;
                }

                // - Foo</i> | - Foo.
                // - Bar.    | - Foo.</i>
                if (!isFixed && Utilities.GetNumberOfLines(cleanText) == 2)
                {
                    var newLineIndex = text.IndexOf(Environment.NewLine, StringComparison.Ordinal);
                    if (newLineIndex > 0)
                    {
                        var firstLine = text.Substring(0, newLineIndex).Trim();
                        var secondLine = text.Substring(newLineIndex + 2).Trim();
                        if (firstLine.EndsWith(endTag, StringComparison.Ordinal))
                        {
                            firstLine = beginTag + firstLine;
                            isFixed = true;
                        }
                        if (secondLine.EndsWith(endTag, StringComparison.Ordinal))
                        {
                            secondLine = beginTag + secondLine;
                            isFixed = true;
                        }
                        text = firstLine + Environment.NewLine + secondLine;
                    }
                }
                if (!isFixed)
                {
                    text = text.Replace(endTag, string.Empty);
                }
            }

            // - foo.</i>
            // - bar.</i>
            if (italicBeginTagCount == 0 && italicEndTagCount == 2 && text.Contains(endTag + Environment.NewLine, StringComparison.Ordinal) && text.EndsWith(endTag, StringComparison.Ordinal))
            {
                text = text.Replace(endTag, string.Empty);
                text = beginTag + text + endTag;
            }

            if (italicBeginTagCount == 0 && italicEndTagCount == 2)
            {
                var firstIndex = text.IndexOf(endTag, StringComparison.Ordinal);
                text = text.Remove(firstIndex, endTag.Length).Insert(firstIndex, beginTag);
            }

            // <i>Foo</i>
            // <i>Bar</i>
            if (italicBeginTagCount == 2 && italicEndTagCount == 2 && noOfLines == 2)
            {
                var index = text.IndexOf(Environment.NewLine, StringComparison.Ordinal);
                if (index > 0 && text.Length > index + (beginTag.Length + endTag.Length))
                {
                    var firstLine = text.Substring(0, index).Trim();
                    var secondLine = text.Substring(index + Environment.NewLine.Length).Trim();

                    if (firstLine.Length > 10 && firstLine.StartsWith("- <i>", StringComparison.Ordinal) && firstLine.EndsWith(endTag, StringComparison.Ordinal))
                    {
                        text = "<i>- " + firstLine.Remove(0, 5) + Environment.NewLine + secondLine;
                        text = text.Replace("<i>-  ", "<i>- ");
                        index = text.IndexOf(Environment.NewLine, StringComparison.Ordinal);
                        firstLine = text.Substring(0, index).Trim();
                        secondLine = text.Substring(index + Environment.NewLine.Length).Trim();
                    }
                    if (secondLine.Length > 10 && secondLine.StartsWith("- <i>", StringComparison.Ordinal) && secondLine.EndsWith(endTag, StringComparison.Ordinal))
                    {
                        text = firstLine + Environment.NewLine + "<i>- " + secondLine.Remove(0, 5);
                        text = text.Replace("<i>-  ", "<i>- ");
                        index = text.IndexOf(Environment.NewLine, StringComparison.Ordinal);
                        firstLine = text.Substring(0, index).Trim();
                        secondLine = text.Substring(index + Environment.NewLine.Length).Trim();
                    }

                    if (Utilities.StartsAndEndsWithTag(firstLine, beginTag, endTag) && Utilities.StartsAndEndsWithTag(secondLine, beginTag, endTag))
                    {
                        text = text.Replace(beginTag, string.Empty).Replace(endTag, string.Empty).Trim();
                        text = beginTag + text + endTag;
                    }
                }

                //FALCONE:<i> I didn't think</i><br /><i>it was going to be you,</i>
                var colIdx = text.IndexOf(':');
                if (colIdx >= 0 && Utilities.CountTagInText(text, beginTag) + Utilities.CountTagInText(text, endTag) == 4 && text.Length > colIdx + 1 && !char.IsDigit(text[colIdx + 1]))
                {
                    var firstLine = text.Substring(0, index);
                    var secondLine = text.Substring(index).TrimStart();

                    var secIdxCol = secondLine.IndexOf(':');
                    if (secIdxCol < 0 || !Utilities.IsBetweenNumbers(secondLine, secIdxCol))
                    {
                        var idx = firstLine.IndexOf(':');
                        if (idx > 1)
                        {
                            var pre = text.Substring(0, idx + 1).TrimStart();

                            /* 项目“LibSE (netstandard2.1)”的未合并的更改
                            在此之前:
                                                        var tempText = text.Remove(0, idx + 1); 

                                                        if (!tempText.StartsWith(']') && !tempText.StartsWith(')'))
                            在此之后:
                                                        var tempText = text.Remove(0, idx + 1);

                                                        if (!tempText.StartsWith(']') && !tempText.StartsWith(')'))
                            */
                            var tempText = text.Remove(0, idx + 1);

                            if (!tempText.StartsWith(']') && !tempText.StartsWith(')'))
                            {
                                text = tempText;
                                text = FixInvalidItalicTags(text).Trim();
                                if (text.StartsWith("<i> ", StringComparison.OrdinalIgnoreCase))
                                {
                                    text = Utilities.RemoveSpaceBeforeAfterTag(text, beginTag);
                                }

                                text = pre + " " + text;
                            }
                        }
                    }
                }
            }

            //<i>- You think they're they gone?<i>
            //<i>- That can't be.</i>
            if (italicBeginTagCount == 3 && italicEndTagCount == 1 && noOfLines == 2)
            {
                var newLineIdx = text.IndexOf(Environment.NewLine, StringComparison.Ordinal);
                var firstLine = text.Substring(0, newLineIdx).Trim();
                var secondLine = text.Substring(newLineIdx).Trim();

                if ((Utilities.StartsAndEndsWithTag(firstLine, beginTag, beginTag) && Utilities.StartsAndEndsWithTag(secondLine, beginTag, endTag)) ||
                    (Utilities.StartsAndEndsWithTag(secondLine, beginTag, beginTag) && Utilities.StartsAndEndsWithTag(firstLine, beginTag, endTag)))
                {
                    text = text.Replace(beginTag, string.Empty);
                    text = text.Replace(endTag, string.Empty);
                    text = text.Replace("  ", " ").Trim();
                    text = beginTag + text + endTag;
                }
            }

            if (noOfLines == 3)
            {
                var lines = text.SplitToLines();
                if ((italicBeginTagCount == 3 && italicEndTagCount == 2) || (italicBeginTagCount == 2 && italicEndTagCount == 3))
                {
                    var numberOfItalics = 0;
                    foreach (var line in lines)
                    {
                        if (line.StartsWith(beginTag, StringComparison.Ordinal))
                        {
                            numberOfItalics++;
                        }

                        if (line.EndsWith(endTag, StringComparison.Ordinal))
                        {
                            numberOfItalics++;
                        }
                    }
                    if (numberOfItalics == 5)
                    { // fix missing tag
                        text = "<i>" + text.Replace("<i>", string.Empty).Replace("</i>", string.Empty) + "</i>";
                    }
                }
            }

            text = text.Replace("<i></i>", string.Empty);
            text = text.Replace("</i><i>", string.Empty);
            if (text.IndexOf('@') < 0)
            {
                text = text.Replace("</i> <i>", "@");
                text = text.Replace("<i> </i>", "@");
                text = text.Replace("<i>  </i>", "@");
                text = text.Replace("@ ", " ");
                text = text.Replace("@ ", " ");
                text = text.Replace(" @", " ");
                text = text.Replace(" @", " ");
                text = text.Replace("@", " ");
            }
            else
            {
                text = text.Replace("</i> <i>", " ");
                text = text.Replace("<i> </i>", " ");
                text = text.Replace("<i>  </i>", " ");
                text = text.Replace("  ", " ");
                text = text.Replace("  ", " ");
            }

            return preTags + text;
        }
    }
}
